package net.wsnetwork.jawa.common;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.Deflater;

import org.apache.commons.codec.binary.Hex;
import org.apache.http.Header;

/**
 * Represents an entry for a specific URI in the HTTP cache.
 *
 * @author cairns@colorado.edu (Brian Cairns)
 */
public class CacheEntry {
    private final CacheControlInfo cacheControlInfo;
    private byte[] data;
    private final Header[] headers;

    public CacheEntry(CacheControlInfo cacheControlInfo, Header[] headers) {
        this.cacheControlInfo = cacheControlInfo;
        this.headers = headers.clone();
    }

    /**
     * Sets the data for this {@link CacheEntry}. The data will be compressed before it is stored.
     */
    public void setData(byte[] data) {
        ByteArrayOutputStream compressedData = new ByteArrayOutputStream();
        try {
            CustomGZIPOutputStream os = new CustomGZIPOutputStream(compressedData);
            os.setLevel(Deflater.BEST_COMPRESSION);
            os.write(data);
            os.close();
            this.data = compressedData.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Sets the data for this {@link CacheEntry} without re-compressing.
     * @param compressedData GZIP-compressed version of the data to store
     */
    public void setDataCompressed(byte[] compressedData) {
        this.data = compressedData;
    }

    public CacheControlInfo getCacheControlInfo() {
        return cacheControlInfo;
    }

    public String getHeaders() {
        StringBuilder response = new StringBuilder();
        response.append("HTTP/1.1 200 OK" + Constants.NEWLINE);
        response.append("Connection: keep-alive\r\n");
        if (data != null) {
            response.append("Content-Length: " + Long.toString(data.length) + Constants.NEWLINE);
            response.append("Etag: " + getEtag() + Constants.NEWLINE);
        } else {
            response.append("Transfer-Encoding: chunked\r\n");
        }
        response.append("Cache-Control: " + cacheControlInfo.getCacheHeader() + Constants.NEWLINE);
        response.append(Constants.SERVER_HEADER);
        response.append("Content-Encoding: gzip\r\n");
        for (Header header : headers) {
            if (!isHeaderReplaced(header)) {
                response.append(header.getName() + ": " + header.getValue() + Constants.NEWLINE);
            }
        }
        response.append(Constants.NEWLINE);
        return response.toString();
    }

    public InputStream getData() {
        return new ByteArrayInputStream(data);
    }

    public InputStream getData(int begin, int end) {
        return new ByteArrayInputStream(data, begin, end - begin);
    }

    public String getEtag() {
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("SHA-1");
            byte[] hash = new byte[40];
            md.update(data, 0, data.length);
            hash = md.digest();
            return Hex.encodeHexString(hash);
        } catch (NoSuchAlgorithmException e) {
            // Should never occur
            throw new RuntimeException(e);
        }
    }

    /**
     * Returns {@code true} if the given header is overridden by the cache process and should not
     * be returned unmodified.
     */
    private static boolean isHeaderReplaced(Header header) {
        String name = header.getName().toLowerCase();
        if (name.equals("connection")) { return true; }
        if (name.equals("cache-control")) { return true; }
        if (name.equals("expires")) { return true; }
        if (name.equals("server")) { return true; }
        if (name.equals("content-encoding")) { return true; }
        if (name.equals("content-length")) { return true; }
        if (name.equals("keep-alive")) { return true; }
        if (name.equals("last-modified")) { return true; }
        if (name.equals("date")) { return true; }
        if (name.equals("etag")) { return true; }
        if (name.equals("accept-ranges")) { return true; }
        return false;
    }
}
